﻿using System;
using System.Linq;
using FlitBit.Parse;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Globalization;

namespace FlitBit.Json.Tests
{
	[TestClass]
	public class JsonParserTests
	{
		[TestMethod]
		public void EmptyJsonObjectShouldParseWithoutError()
		{
			var input = "{}";
			var parser = new JsonTokenizer();
			var output = parser.Tokenize(input);

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
			Assert.AreEqual(2, output.Count(), "Should have exactly 2 tokens.");

			Assert.IsTrue(output.First().Kind == JsonTokenKind.Left_Curly_Brace_Symbol);
			Assert.IsTrue(output.Skip(1).First().Kind == JsonTokenKind.Right_Curly_Brace_Symbol);
		}

		[TestMethod]
		public void EmptyJsonArrayShouldParseWithoutError()
		{
			var input = "[]";
			var parser = new JsonTokenizer();
			var output = parser.Tokenize(input);

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
			Assert.AreEqual(2, output.Count(), "Should have exactly 2 tokens.");

			Assert.IsTrue(output.First().Kind == JsonTokenKind.Left_Brace_Symbol);
			Assert.IsTrue(output.Skip(1).First().Kind == JsonTokenKind.Right_Brace_Symbol);
		}

		[TestMethod]
		public void InvalidSymbolInJsonObjectShouldParseWithError()
		{
			var input = "{:}";
			var parser = new JsonTokenizer();
			var output = parser.Tokenize(input);

			Assert.IsNotNull(output);
			Assert.AreEqual(1, output.Count(t => t.IsError), "Should have 1 (one) parsing errors");
			Assert.AreEqual(3, output.Count(), "Should have exactly 3 tokens.");

			Assert.IsTrue(output.First().Kind == JsonTokenKind.Left_Curly_Brace_Symbol);
			Assert.IsTrue(output.Skip(1).First().IsError);
			Assert.IsTrue(output.Skip(2).First().Kind == JsonTokenKind.Right_Curly_Brace_Symbol);
		}

		[TestMethod]
		public void ObjectWithNumericMembersShouldParseWithoutError()
		{
			var input = @"
{
	number: 1234,
	signed_number: -1234,
	fraction: 12.34,
	signed_fraction: -112.34,
	exponent: 123e+3,
	signed_exponent: +123e+3,	
	fractional_exponent: 12.34E-4,
	signed_fractional_exponent: +12.34E-4  
}";
			var parser = new JsonTokenizer(true);
			var output = parser.Tokenize(input).ToArray();

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
		}

		[TestMethod]
		public void ObjectWithBooleanMembersShouldParseWithoutError()
		{
			var input = @"
{
	""True"": true,
	""False"": false
}";
			var jobj = (JsonObject)JsonValue.Parse(input);
			
			Assert.IsNotNull(jobj);
			Assert.AreEqual(true, (bool)jobj.Where(m => m.Name == "True").First().Value);
			Assert.AreEqual(false, (bool)jobj.Where(m => m.Name == "False").First().Value);
		}

		[TestMethod]
		public void EmptyArrayShouldParseWithoutError()
		{
			var input = @"[]";
			var parser = new JsonTokenizer(true);
			var output = parser.Tokenize(input).ToArray();

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
			Assert.AreEqual(2, output.Count(), "Should have exactly 2 tokens.");

			Assert.IsTrue(output.First().Kind == JsonTokenKind.Left_Brace_Symbol);
			Assert.IsTrue(output.Skip(1).First().Kind == JsonTokenKind.Right_Brace_Symbol);
		}

		[TestMethod]
		public void ArrayOfEmptyObjectsShouldParseWithoutError()
		{
			var input = @"[{},{},{},{}]";
			var parser = new JsonTokenizer(true);
			var output = parser.Tokenize(input).ToArray();

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
		}

		[TestMethod]
		public void ComplexObjectsShouldTokenizeWithoutError()
		{
			var input = @"
{
	Kind: 'AnonymousCash',
	Customer: 
		{
			MobileNumber: '8014143684',
			EmailAddress: 'p@c.com'
		},
	VenueID: 60,
	MenuID: 77,
	Location: 
		{ 
			DeliveryMethod: 'Pickup'
		},
	Items:
		[
			{ ItemID: 1985 },
			{ ItemID: 2188 } 
		]
}
";
			var parser = new JsonTokenizer(true);
			var output = parser.Tokenize(input);

			Assert.IsNotNull(output);
			Assert.AreEqual(0, output.Count(t => t.IsError), "Should have 0 (zero) parsing errors");
		}

		[TestMethod]
		public void JsonConvertShouldConvertComplexJsonObject()
		{
			var input = @"
{ 
	Kind: ""Human"",
	Name: ""Plitk"",
	Age: 33,
	NetWorth: 13.74,
	Children: [ { TooMany: true } ]
}
";
			var node = JsonValue.Parse(input);

			Assert.IsNotNull(node);
			Assert.AreEqual(JsonNodeKind.Object, node.Kind);

			var obj = node as JsonObject;
			var kind = (from m in obj
									where m.Name == "Kind"
									select m.Value).Single();

			Assert.AreEqual(JsonNodeKind.StringLiteral, kind.Kind);
			Assert.AreEqual("Human", (string)kind);

			var age = (from m in obj
									where m.Name == "Age"
									select m.Value).Single();
			Assert.AreEqual(JsonNodeKind.NumericLiteral, age.Kind);
			Assert.AreEqual(33, (int)age);

			var child = (from m in obj
									 where m.Name == "Children"
									 select m.First()).Single();
			Assert.AreEqual(JsonNodeKind.Object, child.Kind);
			
		}

		[TestMethod]
		public void JsonConvertLargeObject()
		{
			var jobj = JsonValue.Parse(@"
{
	""StatusCode"":200,
	""StatusDescription"":"""",
	""ResultItem"":[
			{
				""OrderID"":10158,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20222,
					""MenuItemID"":0,
					""Name"":""Jumbo Diet Pepsi"",
					""Quantity"":1,
					""PricePerItem"":3.0000}],
				""Pincode"":""1234"",
				""Fees"":0.4500,
				""Tip"":0.0,""Taxes"":0.0,
				""Total"":3.4500},
			{
				""OrderID"":10159,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20223,
					""MenuItemID"":2204,
					""Name"":""Cheese Pizza"",
					""Quantity"":1,
					""PricePerItem"":5.5000}],
				""Pincode"":""1234"",
				""Fees"":0.8200,
				""Tip"":0.0,
				""Taxes"":0.0,
				""Total"":6.3200}]}");
			jobj.VisitTransform(n =>
			{
				if (n is JsonMember)
				{
					var member = n as JsonMember;
					if (member.Value.Kind == JsonNodeKind.StringLiteral)
					{
						var lower = member.Name.ToLowerInvariant();
						if (lower.Contains("passw") || lower.Equals("pw"))
						{	
							return (JsonStringLiteral)"*****";
						}						
					}
				}
				else if (n.Kind == JsonNodeKind.StringLiteral)
				{
					var s = n as JsonStringLiteral;
					return (JsonStringLiteral)"xxxxxxxxxxxxxxxxxx";
				}
				return n;
			});
			Assert.IsNotNull(jobj);
		}

		[TestMethod]
		public void JsonDataWithNestedEmptyObjectShouldParse()
		{
			var input = @"{""Controller"":{""ControllerType"":""v2.Controllers.healthController, v2, Version=1.2.700.34080, Culture=neutral, PublicKeyToken=null"",""ControllerName"":""health""},""ActionName"":""Index"",""ActionParameters"":{}}";
			var jobj = JsonValue.Parse(input);
			Assert.IsNotNull(jobj);
		}

		[TestMethod]
		public void JsonDataWithNestedEmptyArrayShouldParse()
		{
			var input = @"{""Controller"":{""ControllerTypes"":[],""Names"":[]}}";
			var jobj = JsonValue.Parse(input);
			Assert.IsNotNull(jobj);
		}

		//[TestMethod]
		public void ParseSomeDates()
		{
			var msJsonDate = Match.MatchExact(CharRule.MatchExact(@"\/Date(".ToCharArray()), CharRule.DecimalDigits, CharRule.MatchExact(@")\/".ToCharArray()));

			Assert.IsFalse(msJsonDate.At("new Date(1234567891011)", (Cursor)0));
			Assert.IsTrue(msJsonDate.At(@"\/Date(1234567891011)\/", (Cursor)0));

			var isoDate = Match.MatchExact(CharRule.Iso8601Year,
				CharRule.MatchExact('-'),
				CharRule.Iso8601Month,
				CharRule.MatchExact('-'),
				CharRule.Iso8601Day,
				CharRule.MatchExact('T'),
				CharRule.Iso8601TimeHours,
				CharRule.MatchExact(':'),
				CharRule.Iso8601TimeMinutes,
				CharRule.MatchExact(':'),
				CharRule.Iso8601TimeSeconds,
				CharRule.Iso8601TimeFractionalSeconds,
				Match.MatchAny(CharRule.Iso8601Timezone, Match.Eof)
				);

			Assert.IsTrue(isoDate.At(@"2009-02-15T00:00:00Z", (Cursor)0));
			Assert.IsTrue(isoDate.At(@"2009-02-15T23:00:00.0000001Z", (Cursor)0));
			Assert.IsFalse(isoDate.At(@"2221-22-15T00:00:00Z", (Cursor)0), "month is invalid");
			Assert.IsTrue(isoDate.At(@"1023-12-15T00:00:00Z", (Cursor)0));
			Assert.IsTrue(isoDate.At(@"2009-01-15T13:08:00.1234567+07:00", (Cursor)0));
			Assert.IsFalse(isoDate.At(@"2009-00-15T13:08:00.1234567+07:00", (Cursor)0), "Zero month is invalid");

			Assert.IsTrue(isoDate.At(@"2009-01-15T13:58:00.1234567-01:00", (Cursor)0));

			var isoCompactDate = Match.MatchExact(CharRule.Iso8601Year,
				CharRule.MatchExact('-'),
				CharRule.Iso8601Month,
				CharRule.MatchExact('-'),
				CharRule.Iso8601Day,
				CharRule.MatchExact('T'),
				CharRule.Iso8601TimeHours,
				CharRule.MatchExact(':'),
				CharRule.Iso8601TimeMinutes,
				CharRule.MatchExact(':'),
				CharRule.Iso8601TimeSeconds,
				Match.MatchAny(CharRule.Iso8601Timezone, Match.Eof)
				);			
		}

		[TestMethod]
		public void ParseAndQueryJDynamic()
		{
			var jobj = JDynamic.Parse(@"
{
	""StatusCode"":200,
	""StatusDescription"":"""",
	""ResultItem"":[
			{
				""OrderID"":10158,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20222,
					""MenuItemID"":0,
					""Name"":""Jumbo Diet Pepsi"",
					""Quantity"":1,
					""PricePerItem"":3.0000}],
				""Pincode"":""1234"",
				""Fees"":0.4500,
				""Tip"":0.0,""Taxes"":0.0,
				""Total"":3.4500},
			{
				""OrderID"":10159,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20223,
					""MenuItemID"":2204,
					""Name"":""Cheese Pizza"",
					""Quantity"":1,
					""PricePerItem"":5.5000}],
				""Pincode"":""1234"",
				""Fees"":0.8200,
				""Tip"":0.0,
				""Taxes"":0.0,
				""Total"":6.3200}]}");			
			Assert.IsNotNull(jobj);
			Assert.AreEqual(200M, jobj.StatusCode);
			Assert.AreEqual(2, jobj.ResultItem.Count);
		}

		[TestMethod]
		public void ParseAndQueryJsonValue()
		{
			var jobj = JsonValue.Parse(@"
{
	""StatusCode"":200,
	""StatusDescription"":"""",
	""ResultItem"":[
			{
				""OrderID"":10158,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20222,
					""MenuItemID"":0,
					""Name"":""Jumbo Diet Pepsi"",
					""Quantity"":1,
					""PricePerItem"":3.0000}],
				""Pincode"":""1234"",
				""Fees"":0.4500,
				""Tip"":0.0,""Taxes"":0.0,
				""Total"":3.4500},
			{
				""OrderID"":10159,
				""EventID"":734,
				""CustomerID"":5031,
				""CustomerCode"":""0787"",
				""MobileNumber"":""8016730787"",
				""DeliveryLocation"":""1-1-1"",
				""Items"":[{
					""ItemID"":20223,
					""MenuItemID"":2204,
					""Name"":""Cheese Pizza"",
					""Quantity"":1,
					""PricePerItem"":5.5000}],
				""Pincode"":""1234"",
				""Fees"":0.8200,
				""Tip"":0.0,
				""Taxes"":0.0,
				""Total"":6.3200}]}") as JsonObject;
			Assert.IsNotNull(jobj);
			Assert.AreEqual(200, (int)jobj.Single(m => m.Name == "StatusCode").Value);			
		}
	}
}
